iT邦幫忙

2023 iThome 鐵人賽

DAY 28
1

Medium

來到 Medium level 我們會發現原本可以讓我們輸入框的地方變成了下拉式清單

https://ithelp.ithome.com.tw/upload/images/20231013/20162775lmaTzw5c41.png

我們再來看看 source code

<?php

if( isset( $_POST[ 'Submit' ] ) ) {
    // Get input
    $id = $_POST[ 'id' ];

    $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

    switch ($_DVWA['SQLI_DB']) {
        case MYSQL:
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
            $result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );

            // Get results
            while( $row = mysqli_fetch_assoc( $result ) ) {
                // Display values
                $first = $row["first_name"];
                $last  = $row["last_name"];

                // Feedback for end user
                echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
            }
            break;
        case SQLITE:
            global $sqlite_db_connection;

            $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
            #print $query;
            try {
                $results = $sqlite_db_connection->query($query);
            } catch (Exception $e) {
                echo 'Caught exception: ' . $e->getMessage();
                exit();
            }

            if ($results) {
                while ($row = $results->fetchArray()) {
                    // Get values
                    $first = $row["first_name"];
                    $last  = $row["last_name"];

                    // Feedback for end user
                    echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                }
            } else {
                echo "Error in fetch ".$sqlite_db->lastErrorMsg();
            }
            break;
    }
}

// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query  = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$number_of_rows = mysqli_fetch_row( $result )[0];

mysqli_close($GLOBALS["___mysqli_ston"]);
?> 

我們可以整理出以下幾點

  • 使用 POST 接 id 的值
  • 把 id 的值經過 mysqli_real_escape_string() 函數處理
  • SQL 的查詢語句是
SELECT first_name, last_name FROM users WHERE user_id = $id;

既然他用 POST 來接 id 的值,那我們可以直接去修改 POST 的值來注入我們的 SQL 語句

我們這裡直接使用 firefox 來修改 POST 的值

首先按 F12 >> Network >> 然後點擊一下 Submit 的按鈕

https://ithelp.ithome.com.tw/upload/images/20231013/20162775FIWqK8uDCj.png

會發現有一個 POST 的請求

https://ithelp.ithome.com.tw/upload/images/20231013/20162775dyqq4KQIez.png

對著 POST 按右鍵 >> 選 Resend

https://ithelp.ithome.com.tw/upload/images/20231013/20162775SOxrx9csZN.png

找到 body 的地方就可以編輯 id 內容了

https://ithelp.ithome.com.tw/upload/images/20231013/20162775jVocpbbv1p.png

這裡改的是1= OR 1=1 -- ,編輯完以後選擇 send

不用單引號是因為 id 的值外面沒有單引號包著,所以不需要閉合

https://ithelp.ithome.com.tw/upload/images/20231013/20162775WFrtFykQXi.png

會發現右邊多了一個 POST 請求,對著 POST 點兩下就可以看 resend 後的結果了

https://ithelp.ithome.com.tw/upload/images/20231013/20162775vv9hKC0oW2.png

https://ithelp.ithome.com.tw/upload/images/20231013/20162775OP7gSjOor8.png

由於我們 id 的值經過mysqli_real_escape_string() 函數處理

mysqli_real_escape_string() 會對單引號 '、 雙引號 " 、 反斜杠 \、NUL(ASCII 0 )這些特殊字元進行轉譯,像'變成\'
https://www.php.net/manual/zh/mysqli.real-escape-string.php

mysql_real_escape_string() 函數只適用於字串類型,如果資料為數字類型,mysql_real_escape_string() 就不會進行轉譯

所以我們要用一些方法繞過它

假設我們今天想知道有哪些資料表

1 UNION SELECT 1,table_name FROM information_schema.tables WHERE table_schema='dvwa' --

我們可以將其中dvwa 的部分轉成 16 進位的數字 0x64767761

1 UNION SELECT 1,table_name FROM information_schema.tables WHERE table_schema=0x64767761 --

https://ithelp.ithome.com.tw/upload/images/20231013/20162775eteYCB1xeF.png

這樣就可以繞過mysqli_real_escape_string()

High

我們可以看到這個畫面

https://ithelp.ithome.com.tw/upload/images/20231013/20162775AJt6E9o1sM.png

點擊後有可以注入的輸入框

https://ithelp.ithome.com.tw/upload/images/20231013/20162775fQHsoABvee.png

分析 source code

 <?php

if( isset( $_SESSION [ 'id' ] ) ) {
    // Get input
    $id = $_SESSION[ 'id' ];

    switch ($_DVWA['SQLI_DB']) {
        case MYSQL:
            // Check database
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
            $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );

            // Get results
            while( $row = mysqli_fetch_assoc( $result ) ) {
                // Get values
                $first = $row["first_name"];
                $last  = $row["last_name"];

                // Feedback for end user
                echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
            }

            ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);        
            break;
        case SQLITE:
            global $sqlite_db_connection;

            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
            #print $query;
            try {
                $results = $sqlite_db_connection->query($query);
            } catch (Exception $e) {
                echo 'Caught exception: ' . $e->getMessage();
                exit();
            }

            if ($results) {
                while ($row = $results->fetchArray()) {
                    // Get values
                    $first = $row["first_name"];
                    $last  = $row["last_name"];

                    // Feedback for end user
                    echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                }
            } else {
                echo "Error in fetch ".$sqlite_db->lastErrorMsg();
            }
            break;
    }
}

?>

這段程式碼是將我們從 session 接收到的 id 直接插入到下面的 SQL 語句中,但也沒有進行任何的過濾處理

SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;

利用了自定義的 Error 訊息 >> 可以防止 Error 注入

但我們還是可以利用 UNION 進行 SQL injection

https://ithelp.ithome.com.tw/upload/images/20231013/20162775KONnvP8QUG.png

https://ithelp.ithome.com.tw/upload/images/20231013/20162775PzfYxFHd0h.png

Impossible

看看 source code


<?php

if( isset( $_GET[ 'Submit' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // Get input
    $id = $_GET[ 'id' ];

    // Was a number entered?
    if(is_numeric( $id )) {
        $id = intval ($id);
        switch ($_DVWA['SQLI_DB']) {
            case MYSQL:
                // Check the database
                $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
                $data->bindParam( ':id', $id, PDO::PARAM_INT );
                $data->execute();
                $row = $data->fetch();

                // Make sure only 1 result is returned
                if( $data->rowCount() == 1 ) {
                    // Get values
                    $first = $row[ 'first_name' ];
                    $last  = $row[ 'last_name' ];

                    // Feedback for end user
                    echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                }
                break;
            case SQLITE:
                global $sqlite_db_connection;

                $stmt = $sqlite_db_connection->prepare('SELECT first_name, last_name FROM users WHERE user_id = :id LIMIT 1;' );
                $stmt->bindValue(':id',$id,SQLITE3_INTEGER);
                $result = $stmt->execute();
                $result->finalize();
                if ($result !== false) {
                    // There is no way to get the number of rows returned
                    // This checks the number of columns (not rows) just
                    // as a precaution, but it won't stop someone dumping
                    // multiple rows and viewing them one at a time.

                    $num_columns = $result->numColumns();
                    if ($num_columns == 2) {
                        $row = $result->fetchArray();

                        // Get values
                        $first = $row[ 'first_name' ];
                        $last  = $row[ 'last_name' ];

                        // Feedback for end user
                        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                    }
                }

                break;
        }
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?>

參數化 SQL 查詢,也就是將輸入視為參數,並嚴格地規定參數應該要有的變數型態

$id = $_GET[ 'id' ];
$id = intval ($id);
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );

這樣能避免 SQL 查詢時 id 不會是字串,以此防止 SQL injection


上一篇
Day 27 SQL Injection (二)
下一篇
Day 29 XXE Injection
系列文
學分的追逐,資安的啟程30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言